package at.codejedi.sto.controller.combat;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.hibernate.Session;
import org.hibernate.Transaction;

import at.codejedi.sto.config.Configuration;
import at.codejedi.sto.controller.build.MainBuildEditorController;
import at.codejedi.sto.controller.lazy.LazyViewController;
import at.codejedi.sto.gui.combat.CombatLogView;
import at.codejedi.sto.model.combat.CombatLog;
import at.codejedi.sto.model.combat.CombatModel;
import at.codejedi.sto.model.combat.IPlayer;
import at.codejedi.sto.model.combat.IPlayerBattle;
import at.codejedi.sto.model.combat.Player;
import at.codejedi.sto.model.combat.PlayerBattle;
import at.codejedi.sto.parser.Parser;
import at.codejedi.sto.storage.HibernateUtil;

public class CombatLogController extends LazyViewController<CombatLogView> {
	private Hashtable<Object, IDetailController> detailControllers = new Hashtable<Object, IDetailController>();

	private ICombatMainController mainController;

	private CombatModel model;

	public CombatLogController(File file, ICombatMainController mainController)
			throws IOException {
		this(mainController);

		Parser parser = new Parser();
		CombatLog log = parser.read(file);
		model = new CombatModel(file, log);

		mainController.addFileToRecentOpenedFiles(file);
		addListener();
	}

	protected CombatLogController(ICombatMainController mainController)
			throws IOException {
		this.mainController = mainController;
	}

	public CombatLogController(int combatLogId,
			ICombatMainController mainController) throws IOException {
		this(mainController);
		Object object = HibernateUtil.getSession().get(CombatModel.class,
				combatLogId);
		model = (CombatModel) object;
	}

	private void addListener() {
		CombatLogView view = getView();
		view.addDetailSelectedActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				@SuppressWarnings("unchecked")
				List<Object> selectedDetails = ((List<Object>) e.getSource());
				detailSelected(selectedDetails);
			}
		});
		view.addCopyToClipboardListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				copyDetailsToClipboard();
			}
		});

		view.addCreateNewBuildActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				createNewBuild();
			}
		});
	}

	public void copyDetailsToClipboard() {
		TextTransfer textTransfer = new TextTransfer();
		String details = getDetailsAsString();
		textTransfer.setClipboardContents(details);

	}

	@Override
	public CombatLogView createLazyView() {
		CombatLogView combatLogView = new CombatLogView();
		combatLogView.addCloseActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				mainController.closeDetailView(getView());
			}
		});
		return combatLogView;
	}

	protected void createNewBuild() {
		MainBuildEditorController mainBuildEditorController = new MainBuildEditorController();
		mainBuildEditorController.creatBuild();
	}

	private void detailSelected(List<Object> selectedDetails) {
		IDetailController controller;
		if (selectedDetails.size() == 1) {
			Object selectedDetail = selectedDetails.get(0);
			if (detailControllers.containsKey(selectedDetail)) {
				controller = detailControllers.get(selectedDetail);
			} else {
				controller = resolveDetailController(selectedDetail);
				detailControllers.put(selectedDetail, controller);
			}
		} else {
			controller = resolveDetailController(selectedDetails);
		}
		if (controller != null)
			getView().displayDetail(controller.getView());
	}

	private String getDamageValueDetails(SortedMap<Double, String> damageMap) {
		StringBuilder builder = new StringBuilder();

		DecimalFormat df = Configuration.getDecimalFormat();

		Set<Double> keySet = damageMap.keySet();
		Double[] keyArray = new Double[keySet.size()];

		keySet.toArray(keyArray);

		Arrays.sort(keyArray, Collections.reverseOrder());

		for (Double key : keyArray) {
			String playerName = damageMap.get(key);

			builder.append(df.format(Math.abs(key)));
			builder.append(" ");
			builder.append(playerName);
			builder.append("\r\n");
		}
		System.out.println(builder.toString());
		return builder.toString();
	}

	private String getDetailsAsString() {
		StringBuilder result = new StringBuilder();

		CombatLog log = model.getParsedLog();
		SortedMap<Double, String> netDamagePerSecondDealtList = new TreeMap<Double, String>();
		SortedMap<Double, String> netDamagePerSecondReceivedList = new TreeMap<Double, String>();

		SortedMap<Double, String> maxNetDamageDealtList = new TreeMap<Double, String>();
		SortedMap<Double, String> maxNetDamageReceivedList = new TreeMap<Double, String>();

		SortedMap<Double, String> averageNetDamageDealtList = new TreeMap<Double, String>();
		SortedMap<Double, String> averageNetDamageReceivedList = new TreeMap<Double, String>();

		SortedMap<Double, String> logEntriesPerSecondList = new TreeMap<Double, String>();

		for (IPlayer player : log.getPlayers()) {
			String playerName = player.getPlayerName();

			double netDamagePerSecondDealt = player.getNetDamageDealt()
					.getDamagePerSecond();
			if (netDamagePerSecondDealtList
					.containsKey(netDamagePerSecondDealt))
				netDamagePerSecondDealt += 0.01d;
			netDamagePerSecondDealtList
					.put(netDamagePerSecondDealt, playerName);

			double netDamagePerSecondReceived = player.getNetDamageReceived()
					.getDamagePerSecond();
			if (netDamagePerSecondReceivedList
					.containsKey(netDamagePerSecondReceived))
				netDamagePerSecondReceived += 0.01d;
			netDamagePerSecondReceivedList.put(netDamagePerSecondReceived,
					playerName);

			double maxNetDamageDealt = player.getNetDamageDealt()
					.getMaxDamage();
			if (logEntriesPerSecondList.containsKey(maxNetDamageDealt))
				maxNetDamageDealt += 0.01d;
			maxNetDamageDealtList.put(maxNetDamageDealt, playerName);

			double maxNetDamageReceived = player.getNetDamageReceived()
					.getMaxDamage();
			if (maxNetDamageReceivedList.containsKey(maxNetDamageReceived))
				maxNetDamageReceived += 0.01d;
			maxNetDamageReceivedList.put(maxNetDamageReceived, playerName);

			double averageNetDamageDealt = player.getNetDamageDealt()
					.getAverageDamage();
			if (averageNetDamageDealtList.containsKey(averageNetDamageDealt))
				averageNetDamageDealt += 0.01d;
			averageNetDamageDealtList.put(averageNetDamageDealt, playerName);

			double averageNetDamageReceived = player.getNetDamageReceived()
					.getAverageDamage();
			if (averageNetDamageReceivedList
					.containsKey(averageNetDamageReceived))
				averageNetDamageReceived += 0.01d;
			averageNetDamageReceivedList.put(averageNetDamageReceived,
					playerName);

			double logEntriesPerSecond = player.getLogEntriesPerSecond();
			if (logEntriesPerSecondList.containsKey(logEntriesPerSecond))
				logEntriesPerSecond += 0.01d;

			logEntriesPerSecondList.put(logEntriesPerSecond, playerName);

		}
		{
			result.append("Net DPS dealt:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(netDamagePerSecondDealtList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}
		{
			result.append("Net DPS received:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(netDamagePerSecondReceivedList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}
		{
			result.append("Max Net damage dealt:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(maxNetDamageDealtList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}

		{
			result.append("Max Net damage received:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(maxNetDamageReceivedList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}

		{
			result.append("Avg. Net damage dealt:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(averageNetDamageDealtList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}

		{
			result.append("Avg. Net damage received:\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(averageNetDamageReceivedList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}

		{
			result.append("Fire Rate (from log entries):\r\n");
			result.append("[INDENT]");
			String values = getDamageValueDetails(logEntriesPerSecondList);
			result.append(values);
			result.append("[/INDENT]\r\n\r\n");
		}

		return result.toString();

	}

	@Override
	public void initialLoaded() {
		CombatLog log = model.getParsedLog();
		getView().displayCombatLog(log);

	}

	public void persist() {
		Session session = HibernateUtil.getSession();
		Transaction beginTransaction = session.beginTransaction();
		try {
			session.save(model);
			beginTransaction.commit();
			session.flush();
		} catch (Exception ex) {
			ex.printStackTrace();
			beginTransaction.rollback();
		} finally {
			session.close();
		}

	}

	private IDetailController resolveDetailController(
			List<Object> selectedDetails) {
		Class<? extends Object> lastClass = null;
		for (Object obj : selectedDetails) {
			if (lastClass == null) {
				lastClass = obj.getClass();
				continue;
			}
			if (lastClass.isInstance(obj))
				continue;
			else
				return null;
		}
		if (lastClass.equals(Player.class)) {
			List<IPlayer> players = new ArrayList<IPlayer>();
			for (Object obj : selectedDetails) {
				players.add((IPlayer) obj);
			}
			return new PlayerCompareController(players, mainController);
		}
		if (lastClass.equals(PlayerBattle.class)) {
			List<IPlayerBattle> battles = new ArrayList<IPlayerBattle>();
			for (Object obj : selectedDetails) {
				battles.add((IPlayerBattle) obj);
			}
			return new BattleCompareController(battles, mainController);
		}
		throw new IllegalArgumentException("selectedDetails");
	}

	private IDetailController resolveDetailController(Object selectedDetail) {
		if (selectedDetail instanceof Player) {
			IPlayer p = (IPlayer) selectedDetail;
			return new PlayerController(p);
		}
		if (selectedDetail instanceof IPlayerBattle) {
			PlayerBattle b = (PlayerBattle) selectedDetail;
			return new BattleController(b);
		}
		throw new IllegalArgumentException("selectedDetail");
	}

}
